﻿using Microsoft.AspNet.Identity;
using MVCForum.Web.Identity.EF.Entities;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace MVCForum.Web.Identity.EF {

	public class UserStore : IUserStore<User>,
							IDisposable,
							IUserPasswordStore<User>,
							IUserRoleStore<User>,
							IUserClaimStore<User>,
							IUserLoginStore<User>,
							IUserSecurityStampStore<User> {

		private Boolean disposed;
		private EntityStore<User> userStore;
		private EntityStore<Role> roleStore;
		private IDbSet<UserRole> userRoles;
		private IDbSet<UserClaim> userClaims;
		private IDbSet<UserLogin> logins;
		private DbContext context;

		public UserStore(DbContext context) {
			if (context == null) {
				throw new ArgumentNullException("context");
			}
			this.context = context;
			this.userStore = new EntityStore<User>(context);
			this.roleStore = new EntityStore<Role>(context);
			this.logins = context.Set<UserLogin>();
			this.userClaims = context.Set<UserClaim>();
			this.userRoles = context.Set<UserRole>();
		}

		public Task CreateAsync(User user) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}

			user.Approved = false;
			user.Created = DateTime.UtcNow;
			user.LastLockout = DateTime.MinValue;
			user.LastPasswordFailure = DateTime.UtcNow;
			user.LastVisit = DateTime.UtcNow;
			user.Locked = false;
			user.PasswordFailures = 0;

			this.userStore.Create(user);
			this.context.SaveChanges();

			return Task.FromResult<Object>(null);
		}

		public Task DeleteAsync(User user) {
			throw new NotImplementedException();
		}

		public Task<User> FindByIdAsync(String userId) {
			return this.userStore.GetByIdAsync(userId);
		}

		public Task<User> FindByNameAsync(String userName) {
			IQueryable<User> entitySet = this.userStore.EntitySet.Where(u => u.UserName.ToUpper() == userName.ToUpper());
			return Task.FromResult<User>(entitySet.FirstOrDefault<User>());
		}

		public Task UpdateAsync(User user) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			this.context.SaveChanges();
			return Task.FromResult<Object>(null);
		}

		public void Dispose() {
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing) {
			if (disposing && this.context != null) {
				this.context.Dispose();
			}
			this.disposed = true;
			this.context = null;
			this.userStore = null;
		}

		public Task<String> GetPasswordHashAsync(User user) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			return Task.FromResult<String>(user.PasswordHash);
		}

		public Task<Boolean> HasPasswordAsync(User user) {
			return Task.FromResult<Boolean>(user.PasswordHash != null);
		}

		public Task SetPasswordHashAsync(User user, String passwordHash) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			user.PasswordHash = passwordHash;
			this.context.SaveChanges();
			return Task.FromResult<Int32>(0);
		}

		public Task AddToRoleAsync(User user, String roleName) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			if (String.IsNullOrWhiteSpace(roleName)) {
				throw new ArgumentNullException("role");
			}
			Role identityRole = this.roleStore.EntitySet.SingleOrDefault(r => r.Name.ToUpper() == roleName.ToUpper());
			if (identityRole == null) {
				throw new InvalidOperationException("role not found");
			}
			UserRole identityUserRole = new UserRole() {
				User = user,
				Role = identityRole
			};
			// TODO: hm...
			this.context.Set<UserRole>().Add(identityUserRole);
			this.context.SaveChanges();
			return Task.FromResult<Int32>(0);
		}

		public Task<IList<String>> GetRolesAsync(User user) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			return Task.FromResult<IList<String>>(user.Roles.Select(r => r.Role.Name).ToList());
		}

		public Task<Boolean> IsInRoleAsync(User user, String roleName) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			if (String.IsNullOrWhiteSpace(roleName)) {
				throw new ArgumentException("roleName");
			}
			return Task.FromResult<Boolean>(user.Roles.Any(r => r.Role.Name.ToUpper() == roleName.ToUpper()));
		}

		public Task RemoveFromRoleAsync(User user, String roleName) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			if (String.IsNullOrWhiteSpace(roleName)) {
				throw new ArgumentNullException("roleName");
			}
			UserRole identityUserRole = user.Roles.Where(r => r.Role.Name.ToUpper() == roleName.ToUpper()).FirstOrDefault();
			if (identityUserRole != null) {
				user.Roles.Remove(identityUserRole);
				this.userRoles.Remove(identityUserRole);
			}
			this.context.SaveChanges();
			return Task.FromResult<int>(0);
		}

		public Task AddClaimAsync(User user, Claim claim) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			if (claim == null) {
				throw new ArgumentNullException("claim");
			}
			UserClaim identityUserClaim = new UserClaim() {
				User = user,
				ClaimType = claim.Type,
				ClaimValue = claim.Value
			};
			user.Claims.Add(identityUserClaim);
			this.context.SaveChanges();
			return Task.FromResult<Int32>(0);
		}

		public Task<IList<Claim>> GetClaimsAsync(User user) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			IList<Claim> claims = new List<Claim>();
			foreach (UserClaim claim in user.Claims) {
				claims.Add(new Claim(claim.ClaimType, claim.ClaimValue));
			}
			return Task.FromResult<IList<Claim>>(claims);
		}

		public Task RemoveClaimAsync(User user, Claim claim) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			if (claim == null) {
				throw new ArgumentNullException("claim");
			}
			List<UserClaim> list = user.Claims.Where<UserClaim>(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToList<UserClaim>();
			foreach (UserClaim identityUserClaim in list) {
				user.Claims.Remove(identityUserClaim);
				this.userClaims.Remove(identityUserClaim);
			}
			this.context.SaveChanges();
			return Task.FromResult<Int32>(0);
		}

		public Task AddLoginAsync(User user, UserLoginInfo login) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			if (login == null) {
				throw new ArgumentNullException("login");
			}
			UserLogin identityUserLogin = new UserLogin() {
				User = user,
				ProviderKey = login.ProviderKey,
				LoginProvider = login.LoginProvider
			};
			user.Logins.Add(identityUserLogin);
			this.context.SaveChanges();
			return Task.FromResult<Int32>(0);
		}

		public Task<User> FindAsync(UserLoginInfo login) {
			if (login == null) {
				throw new ArgumentNullException("login");
			}
			IQueryable<UserLogin> loginProvider = this.logins.Where(l => l.LoginProvider == login.LoginProvider && l.ProviderKey == login.ProviderKey);
			return Task.FromResult(loginProvider.Select(l => l.User).FirstOrDefault());
		}

		public Task<IList<UserLoginInfo>> GetLoginsAsync(User user) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			IList<UserLoginInfo> userLoginInfos = new List<UserLoginInfo>();
			foreach (UserLogin login in user.Logins) {
				userLoginInfos.Add(new UserLoginInfo(login.LoginProvider, login.ProviderKey));
			}
			return Task.FromResult<IList<UserLoginInfo>>(userLoginInfos);
		}

		public Task RemoveLoginAsync(User user, UserLoginInfo login) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			if (login == null) {
				throw new ArgumentNullException("login");
			}
			// TODO: Hm....
			UserLogin userLogin = user.Logins.Where(l => l.ProviderKey == login.ProviderKey).SingleOrDefault();
			if (userLogin != null) {
				user.Logins.Remove(userLogin);
				this.logins.Remove(userLogin);
			}
			this.context.SaveChanges();
			return Task.FromResult<Int32>(0);
		}

		public Task<String> GetSecurityStampAsync(User user) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			return Task.FromResult<String>(user.SecurityStamp);
		}

		public Task SetSecurityStampAsync(User user, String stamp) {
			if (user == null) {
				throw new ArgumentNullException("user");
			}
			user.SecurityStamp = stamp;
			this.context.SaveChanges();
			return Task.FromResult<Int32>(0);
		}
	}
}